package org.jfrog.bamboo.promotion;
import com.atlassian.bamboo.plan.PlanIdentifier;
import com.atlassian.bamboo.variable.VariableDefinition;
import com.atlassian.bamboo.variable.VariableDefinitionManager;
import com.google.common.collect.Maps;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.codehaus.jackson.map.introspect.JacksonAnnotationIntrospector;
import org.jfrog.bamboo.release.action.ReleaseAndPromotionAction;
import org.jfrog.bamboo.util.ActionLog;
import org.jfrog.build.api.BuildInfoFields;
import org.jfrog.build.api.builder.PromotionBuilder;
import org.jfrog.build.extractor.clientConfiguration.client.ArtifactoryBuildInfoClient;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import static org.jfrog.bamboo.release.action.ReleaseAndPromotionAction.*;
/**
* Executes the promotion process
*
* @author Noam Y. Tenne
*/
public class PromotionThread extends Thread {
public static final String NEXUS_PUSH_PROPERTY_PREFIX = "bintrayOsoPush.";
transient Logger log = Logger.getLogger(PromotionThread.class);
private ReleaseAndPromotionAction action;
private ArtifactoryBuildInfoClient client;
private String bambooUsername;
private ActionLog releaseLog;
public PromotionThread(ReleaseAndPromotionAction action, ArtifactoryBuildInfoClient client,
String bambooUsername) {
this.action = action;
this.client = client;
this.bambooUsername = bambooUsername;
this.releaseLog = ReleaseAndPromotionAction.promotionContext.getActionLog();
releaseLog.setLogger(log);
}
@Override
public void run() {
try {
promotionContext.getLock().lock();
promotionContext.setBuildKey(action.getBuildKey());
promotionContext.setBuildNumber(action.getBuildNumber());
promotionContext.setDone(false);
promotionContext.getLog().clear();
if (performPromotion() && PROMOTION_PUSH_TO_NEXUS_MODE.equals(action.getPromotionMode())) {
executePushToNexusPlugin();
}
} catch (Exception e) {
String message = "An error occurred: " + e.getMessage();
releaseLog.logError(message, e);
} finally {
try {
client.close();
} finally {
promotionContext.setDone(true);
promotionContext.getLock().unlock();
}
}
}
private boolean executePushToNexusPlugin() throws IOException {
releaseLog.logError("Executing 'Promotion to Bintray and Central' plugin ...");
VariableDefinitionManager varDefManager = action.getVariableDefinitionManager();
PlanIdentifier planIdentifier = action.getPlanManager().getPlanIdentifierForPermissionCheckingByKey(action.getPlanKey());
if (planIdentifier == null) {
String message = "Plugin execution failed: Couldn't find nexusPush variables.<br/>";
releaseLog.logError(message);
return false;
}
Map<String, String> executeRequestParams = Maps.newHashMap();
executeRequestParams.put(BuildInfoFields.BUILD_NAME, action.getImmutableBuild().getName());
executeRequestParams.put(BuildInfoFields.BUILD_NUMBER, action.getBuildNumber().toString());
List<VariableDefinition> planVariables = varDefManager.getPlanVariables(planIdentifier);
for (VariableDefinition planVariable : planVariables) {
String key = planVariable.getKey();
if (StringUtils.isNotBlank(key) && key.startsWith(NEXUS_PUSH_PROPERTY_PREFIX)) {
executeRequestParams.put(StringUtils.removeStart(key, NEXUS_PUSH_PROPERTY_PREFIX),
planVariable.getValue());
}
}
HttpResponse nexusPushResponse = null;
try {
nexusPushResponse = client.executePromotionUserPlugin(NEXUS_PUSH_PLUGIN_NAME, action.getImmutableBuild().getName(),
action.getBuildNumber().toString(), null);
StatusLine responseStatusLine = nexusPushResponse.getStatusLine();
if (HttpStatus.SC_OK == responseStatusLine.getStatusCode()) {
releaseLog.logMessage("Plugin successfully executed!");
return true;
} else {
String responseContent = entityToString(nexusPushResponse);
String message = "Plugin execution failed: " + responseStatusLine + "<br/>" + responseContent;
releaseLog.logError(message);
return false;
}
} finally {
if (nexusPushResponse != null) {
HttpEntity entity = nexusPushResponse.getEntity();
if (entity != null) {
EntityUtils.consume(entity);
}
}
}
}
private boolean performPromotion() throws IOException {
releaseLog.logMessage("Promoting build ...");
// do a dry run first
PromotionBuilder promotionBuilder = new PromotionBuilder().status(action.getTarget())
.comment(action.getComment()).ciUser(bambooUsername).targetRepo(action.getPromotionRepo())
.dependencies(action.isIncludeDependencies()).copy(action.isUseCopy())
.dryRun(true);
releaseLog.logMessage("Performing dry run promotion (no changes are made during dry run) ...");
String buildName = action.getImmutableBuild().getName();
String buildNumber = action.getBuildNumber().toString();
HttpResponse dryResponse = null;
HttpResponse wetResponse = null;
try {
dryResponse = client.stageBuild(buildName, buildNumber, promotionBuilder.build());
if (checkSuccess(dryResponse, true)) {
releaseLog.logMessage("Dry run finished successfully. Performing promotion ...");
wetResponse = client.stageBuild(buildName, buildNumber, promotionBuilder.dryRun(false).build());
if (checkSuccess(wetResponse, false)) {
releaseLog.logMessage("Promotion completed successfully!");
return true;
}
return false;
}
return false;
} finally {
if (dryResponse != null) {
HttpEntity entity = dryResponse.getEntity();
if (entity != null) {
EntityUtils.consume(entity);
}
}
if (wetResponse != null) {
HttpEntity entity = wetResponse.getEntity();
if (entity != null) {
EntityUtils.consume(entity);
}
}
}
}
/**
* Checks the status and return true on success
*
* @param response
* @param dryRun
* @return
*/
private boolean checkSuccess(HttpResponse response, boolean dryRun) throws IOException {
StatusLine status = response.getStatusLine();
String content = entityToString(response);
if (status.getStatusCode() != 200) {
if (dryRun) {
String message = "Promotion failed during dry run (no change in Artifactory was done): " +
status + "<br/>" + content;
releaseLog.logMessage(message);
} else {
String message = "Promotion failed. View Artifactory logs for more details: " + status +
"<br/>" + content;
releaseLog.logError(message);
}
return false;
}
JsonFactory factory = createJsonFactory();
JsonParser parser = factory.createJsonParser(content);
JsonNode root = parser.readValueAsTree();
JsonNode messagesNode = root.get("messages");
for (JsonNode node : messagesNode) {
String level = node.get("level").getTextValue();
String message = node.get("message").getTextValue();
if (("WARNING".equals(level) || "ERROR".equals(level)) && !message.startsWith("No items were")) {
String errorMessage = "Received " + level + ": " + message;
releaseLog.logError(errorMessage);
return false;
}
}
return true;
}
private JsonFactory createJsonFactory() {
JsonFactory jsonFactory = new JsonFactory();
ObjectMapper mapper = new ObjectMapper(jsonFactory);
mapper.getSerializationConfig().setAnnotationIntrospector(new JacksonAnnotationIntrospector());
mapper.getSerializationConfig().setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
jsonFactory.setCodec(mapper);
return jsonFactory;
}
private String entityToString(HttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
InputStream is = entity.getContent();
return IOUtils.toString(is, "UTF-8");
}
}